#ifndef WIDGET_H
#define WIDGET_H

#include "../gui_base.h"

class Widget;

class WidgetListener
{
public:
    virtual void OnShow(Widget* w) = 0;
    virtual void OnHide(Widget* w) = 0;
};

class Widget: public GuiBase, public Observable< WidgetListener >, public WidgetListener
{
public:
    /* ----------------------- constructor and destructor -------------------------------------- */
    Widget(Widget* _parent, Engine* _engine, int _x, int _y, int _w, int _h)
        :GuiBase(_engine, _x, _y)
    {
        parent = _parent;
        if(parent != 0)
        {
            x += parent->x;
            y += parent->y;
            sx = x;
            sy = y;
        }
        w = _w;
        h = _h;

        if( parent != 0 )
        {
            parentRect = QRect( parent->x, parent->y, parent->w, parent->h );
        } else
        {
            parentRect = QRect( 0, 0, w, h );
        }
        myRect = QRect( x, y, w, h );

        paintEnabled = false;
        visible = false;
        insidePress = false;
        insideRelease = false;
        invalidated = true;

        eventCount = 0;
        showingChildren = false;
        hidingChildren = false;

        xGetter = new PGetter(this, X_PROPERTY);
        xSetter = new PSetter(this, X_PROPERTY);

        yGetter = new PGetter(this, Y_PROPERTY);
        ySetter = new PSetter(this, Y_PROPERTY);

        wGetter = new PGetter(this, W_PROPERTY);
        wSetter = new PSetter(this, W_PROPERTY);

        hGetter = new PGetter(this, H_PROPERTY);
        hSetter = new PSetter(this, H_PROPERTY);

        Interpolable::RegisterProperty<int>(this, "x", xGetter, xSetter, &invalidated);
        Interpolable::RegisterProperty<int>(this, "y", yGetter, ySetter, &invalidated);
        Interpolable::RegisterProperty<int>(this, "width", wGetter, wSetter, &invalidated);
        Interpolable::RegisterProperty<int>(this, "height", hGetter, hSetter, &invalidated);
    }

    virtual ~Widget()
    {
        delete xGetter;
        delete xSetter;

        delete yGetter;
        delete ySetter;

        delete wGetter;
        delete wSetter;

        delete hGetter;
        delete hSetter;

        ClearChildren();
    }
    /* ----------------------------------------------------------------------------------------- */

    /* --------------------- position support -------------------------------------------------- */
    virtual int GetX(){ return x - parent->x; }
    virtual void SetX(int newX)
    {
        int diff = x - newX;
        sw += diff;
        x = parent->x + newX;
        invalidated = true;
    }
    virtual int GetY(){ return y - parent->y; }
    virtual void SetY(int newY)
    {
        int diff = y - newY;
        sh += diff;
        y = parent->y + newY;
        invalidated = true;
    }

    virtual int GetWidth(){ return w; }
    virtual int GetHeight(){ return h; }
    virtual void SetWidth(int newWidth)
    {
        w = newWidth;
        sw = w;
        invalidated = true;
    }

    virtual void SetHeight(int newHeight)
    {
        h = newHeight;
        sh = h;
        invalidated = true;
    }
    /* ----------------------------------------------------------------------------------------- */

    /* ------------------------------ children managing ---------------------------------------- */
    virtual void AddChild(Widget* w)
    {
        children.push_back(w);
        w->AddObserver(this);
        invalidated = true;
    }

    virtual void ClearChildren()
    {
        for(vector< Widget* >::iterator i=children.begin(); i!=children.end(); ++i)
        {
            delete (*i);
        }
        children.clear();
        invalidated = true;
    }

    virtual void RemoveChild(Widget* w)
    {
        vector< Widget* >::iterator i = find(children.begin(), children.end(), w);
        if( i != children.end() )
        {
            delete (*i);
            children.erase(i);
        }
        invalidated = true;
    }
    /* ----------------------------------------------------------------------------------------- */

    /* ------------------------------- status handling ----------------------------------------- */
    virtual bool IsVisible(){ return visible; }
    virtual void SetVisible(bool set)
    {
        if(set)
            Show();
        else
            Hide();
    }

    virtual void Show()
    {
        if(visible)
        {
            Showed();
            return;
        }
        doShow();
    }

    virtual void Hide()
    {
        if(!visible)
        {
            Hidden();
            return;
        }

        if(children.size() > 0)
        {
            eventCount = children.size();
            for(vector< Widget* >::iterator i=children.begin(); i!=children.end(); ++i)
            {
                (*i)->Hide();
            }
        } else
        {
            doHide();
        }
    }

protected:
    virtual void Showed()
    {
        if(children.size() == 0)
        {
            TriggerEvent(0);
        }else
        {
            eventCount = children.size();
            for(vector< Widget* >::iterator i=children.begin(); i!=children.end(); ++i)
            {
                (*i)->Show();
            }
        }
    }

    virtual void Hidden()
    {
        paintEnabled = false;
        TriggerEvent(1);
    }

    virtual void doShow()
    {
        visible = true;
        paintEnabled = true;

        Showed();
    }

    virtual void doHide()
    {
        visible = false;
        Hidden();
    }
    /* ----------------------------------------------------------------------------------------- */

    /* ---------------- WidgetListener implementation ------------------------------------------ */
    virtual void OnShow(Widget* w)
    {
        eventCount--;
        if(eventCount == 0)
        {
            TriggerEvent(0);
        }
    };

    virtual void OnHide(Widget* w)
    {
        eventCount--;
        if(eventCount == 0)
        {
            doHide();
        }
    };
    /* ----------------------------------------------------------------------------------------- */

public:
    /* ------------------------------ GuiBase implementation ----------------------------------- */
    virtual void Draw(QPainter &painter)
    {
        if( !paintEnabled ) return;

        Paint(painter);
        for(vector< Widget* >::iterator i=children.begin(); i!=children.end(); ++i)
        {
            (*i)->Draw(painter);
        }
        //invalidated = false;
    }

    virtual void DoQWidgetOperations()
    {
        if( children.size() > 0 )
        {
            for(vector< Widget* >::iterator i=children.begin(); i!=children.end(); ++i)
            {
                (*i)->DoQWidgetOperations();
            }
        }
    }
    /* ----------------------------------------------------------------------------------------- */

    /* ---------------------- mouse events handling -------------------------------------------- */
    virtual void MousePressed(QMouseEvent* event)
    {
        if(!visible) return;
        for(vector< Widget* >::iterator i=children.begin(); i!=children.end(); ++i)
        {
            (*i)->MousePressed(event);
        }
        int x_pos = event->x(), y_pos = event->y();
        int x_rel = x_pos - x, y_rel = y_pos - y;
        insidePress = ( x_pos >= x && x_pos <= x + w && y_pos >= y && y_pos <= y + h);
        OnMousePressed(event, insidePress, insidePress? x_rel : -1, insidePress? y_rel : -1);
    };

    virtual void MouseReleased(QMouseEvent* event)
    {
        if(!visible) return;
        for(vector< Widget* >::iterator i=children.begin(); i!=children.end(); ++i)
        {
            (*i)->MouseReleased(event);
        }
        int x_pos = event->x(), y_pos = event->y();
        int x_rel = x_pos - x, y_rel = y_pos - y;
        insideRelease = ( x_pos >= x && x_pos <= x + w && y_pos >= y && y_pos <= y + h);
        OnMouseReleased(event, insideRelease, insideRelease? x_rel : -1, insideRelease? y_rel : -1);

        if(insidePress && insideRelease)
        {
            OnMouseClick(event, x_rel, y_rel);
        }
    };

    virtual void MouseMoved(QMouseEvent* event)
    {
        if(!visible) return;
        int x_pos = event->x(), y_pos = event->y();
        int x_rel = x_pos - x, y_rel = y_pos - y;
        bool onMe = (x_pos >= x && x_pos <= x + w && y_pos >= y && y_pos <= y + h);

        for(vector< Widget* >::iterator i=children.begin(); i!=children.end(); ++i)
        {
            if((*i)->mouseTracking)
            {
                (*i)->MouseMoved(event);
            }
        }
        OnMouseMoved(event, onMe, onMe? x_rel : -1, onMe? y_rel : -1);
    };
    /* ----------------------------------------------------------------------------------------- */

    /* ----------------------- function to override/implement ---------------------------------- */
    virtual void Paint(QPainter& painter){};  // paint function

    // mouse events (subwidgets may override these events)
    virtual void OnMousePressed(QMouseEvent* e, bool inside, int x_rel, int y_rel){};
    virtual void OnMouseReleased(QMouseEvent* e, bool inside, int x_rel, int y_rel){};
    virtual void OnMouseClick(QMouseEvent* e, int x_rel, int y_rel){};
    virtual void OnMouseMoved(QMouseEvent* e, bool onWidget, int x_rel, int y_rel){};
    /* ----------------------------------------------------------------------------------------- */

protected:
    /* --------  PropertyGetter and Setter for setting x and y properties ---------------------- */
    class PSetter: public PropertySetter<int>
    {
    public:
        PSetter(Widget* _owner, int _p):owner(_owner), p(_p){}
        ~PSetter(){}

        void operator()(const int& val)
        {
            switch(p)
            {
            case 0:
                owner->SetX(val);
                break;
            case 1:
                owner->SetY(val);
                break;
            case 2:
                owner->SetWidth(val);
                break;
            case 3:
                owner->SetHeight(val);
                break;
            }
        }

    private:
        Widget* owner;
        int p;
    };

    class PGetter: public PropertyGetter<int>
    {
    public:
        PGetter(Widget* _owner, int _p):owner(_owner), p(_p){}
        ~PGetter(){}

        int operator()()
        {
            int toRet = -1;
            switch(p)
            {
            case 0:
                toRet = owner->GetX();
                break;
            case 1:
                toRet = owner->GetY();
                break;
            case 2:
                toRet = owner->GetWidth();
                break;
            case 3:
                toRet = owner->GetHeight();
                break;
            }
            return toRet;
        }

    private:
        Widget* owner;
        int p;
    };
    /* ----------------------------------------------------------------------------------------- */

    /* --------------------------- protected fields -------------------------------------------- */
    Widget* parent;
    vector< Widget* > children;
    QRect parentRect;
    QRect myRect;

    bool paintEnabled;
    bool visible;
    bool insidePress;
    bool insideRelease;

    PGetter* xGetter;
    PSetter* xSetter;

    PGetter* yGetter;
    PSetter* ySetter;

    PGetter* wGetter;
    PSetter* wSetter;

    PGetter* hGetter;
    PSetter* hSetter;

    const static int X_PROPERTY = 0;
    const static int Y_PROPERTY = 1;
    const static int W_PROPERTY = 2;
    const static int H_PROPERTY = 3;

    int eventCount;
    /* ----------------------------------------------------------------------------------------- */

    /* --------------------------- protected functions ----------------------------------------- */
    virtual void TriggerEvent(int eventId)
    {
        for(vector< WidgetListener* >::iterator i=observers->begin(); i!=observers->end(); ++i)
        {
            switch(eventId)
            {
            case 0:
                (*i)->OnShow(this);
                break;
            case 1:
                (*i)->OnHide(this);
                break;
            }
        }
    }
    /* ----------------------------------------------------------------------------------------- */

private:
    bool hidingChildren;
    bool showingChildren;
};

#endif // WIDGET_H
